﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace SkypeMonitorService
{
	class Win32
	{
		#region "CONTS"

		internal const UInt32 INFINITE = 0xFFFFFFFF;
		internal const UInt32 WAIT_FAILED = 0xFFFFFFFF;

		#endregion

		#region "ENUMS"

		[Flags]
		internal enum LogonType
		{
			LOGON32_LOGON_INTERACTIVE = 2,
			LOGON32_LOGON_NETWORK = 3,
			LOGON32_LOGON_BATCH = 4,
			LOGON32_LOGON_SERVICE = 5,
			LOGON32_LOGON_UNLOCK = 7,
			LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
			LOGON32_LOGON_NEW_CREDENTIALS = 9
		}

		[Flags]
		internal enum LogonProvider
		{
			LOGON32_PROVIDER_DEFAULT = 0,
			LOGON32_PROVIDER_WINNT35,
			LOGON32_PROVIDER_WINNT40,
			LOGON32_PROVIDER_WINNT50
		}

		#endregion

		#region "STRUCTS"

		[StructLayout(LayoutKind.Sequential)]
		internal struct STARTUPINFO
		{
			public Int32 cb;
			public String lpReserved;
			public String lpDesktop;
			public String lpTitle;
			public Int32 dwX;
			public Int32 dwY;
			public Int32 dwXSize;
			public Int32 dwYSize;
			public Int32 dwXCountChars;
			public Int32 dwYCountChars;
			public Int32 dwFillAttribute;
			public Int32 dwFlags;
			public Int16 wShowWindow;
			public Int16 cbReserved2;
			public IntPtr lpReserved2;
			public IntPtr hStdInput;
			public IntPtr hStdOutput;
			public IntPtr hStdError;
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct PROCESS_INFORMATION
		{
			public IntPtr hProcess;
			public IntPtr hThread;
			public Int32 dwProcessId;
			public Int32 dwThreadId;
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct SECURITY_ATTRIBUTES
		{
			public int nLength;
			public IntPtr lpSecurityDescriptor;
			public bool bInheritHandle;
		}

		internal enum SECURITY_IMPERSONATION_LEVEL
		{
			SecurityAnonymous,
			SecurityIdentification,
			SecurityImpersonation,
			SecurityDelegation
		}

		#endregion

		#region "FUNCTIONS (P/INVOKE)"

		[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		internal static extern Boolean LogonUser(String lpszUserName, String lpszDomain, String lpszPassword,
			LogonType dwLogonType, LogonProvider dwLogonProvider, out IntPtr phToken);

		[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		internal static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess, ref SECURITY_ATTRIBUTES lpTokenAttributes,
														SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, int TokenType, out IntPtr phNewToken);

		[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
		internal static extern Boolean CreateProcessAsUser
		(
			IntPtr hToken,
			String lpApplicationName,
			String lpCommandLine,
			IntPtr lpProcessAttributes,
			IntPtr lpThreadAttributes,
			Boolean bInheritHandles,
			Int32 dwCreationFlags,
			IntPtr lpEnvironment,
			String lpCurrentDirectory,
			ref STARTUPINFO lpStartupInfo,
			out PROCESS_INFORMATION lpProcessInformation
		);

		[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
		internal static extern Boolean CreateProcessWithLogonW
		(
			String lpszUsername,
			String lpszDomain,
			String lpszPassword,
			Int32 dwLogonFlags,
			String applicationName,
			String commandLine,
			Int32 creationFlags,
			IntPtr environment,
			String currentDirectory,
			ref STARTUPINFO sui,
			out PROCESS_INFORMATION processInfo
		);

		[DllImport("kernel32.dll", SetLastError = true)]
		internal static extern UInt32 WaitForSingleObject
		(
			IntPtr hHandle,
			UInt32 dwMilliseconds
		);

		[DllImport("kernel32", SetLastError = true)]
		internal static extern Boolean CloseHandle(IntPtr handle);

		#endregion

		#region "FUNCTIONS"

		public static void LaunchCommand1(string strCommand, string strDomain, string strName, string strPassword)
		{
			// Variables
			PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
			STARTUPINFO startInfo = new STARTUPINFO();
			bool bResult = false;
			UInt32 uiResultWait = WAIT_FAILED;

			try
			{
				// Create process
				startInfo.cb = Marshal.SizeOf(startInfo);

				bResult = CreateProcessWithLogonW(strName, strDomain, strPassword,
					0,
					null,
					strCommand,
					0,
					IntPtr.Zero,
					null,
					ref startInfo,
					out processInfo
				);
				if (!bResult)
					throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());

				// Wait for process to end
				uiResultWait = WaitForSingleObject(processInfo.hProcess, INFINITE);
				if (uiResultWait == WAIT_FAILED)
					throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());

			}
			finally
			{
				// Close all handles
				CloseHandle(processInfo.hProcess);
				CloseHandle(processInfo.hThread);
			}
		}

		public static void LaunchCommand2(string strCommand, string strDomain, string strName, string strPassword)
		{
			// Variables
			PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
			STARTUPINFO startInfo = new STARTUPINFO();
			Boolean bResult = false;
			IntPtr hToken = IntPtr.Zero;
			IntPtr DuplicatedToken = IntPtr.Zero;
			UInt32 uiResultWait = WAIT_FAILED;
			System.Security.Principal.WindowsImpersonationContext impersonatedUser = null;
			
			try
			{
				// Logon user
				bResult = Win32.LogonUser(strName, strDomain, strPassword,
											Win32.LogonType.LOGON32_LOGON_INTERACTIVE,
											Win32.LogonProvider.LOGON32_PROVIDER_DEFAULT,
											out hToken);
				if (!bResult)
					throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());

				SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
				sa.bInheritHandle = false;
				sa.nLength = Marshal.SizeOf(sa);
				sa.lpSecurityDescriptor = (IntPtr)0;
				uint GENERIC_ALL = 0x10000000;
				int TokenPrimary = 1;

				bool bRetVal = DuplicateTokenEx(hToken, GENERIC_ALL, ref sa, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TokenPrimary, out DuplicatedToken);
				if (bRetVal == false)
				{
					int ret = Marshal.GetLastWin32Error();
					CloseHandle(hToken);
					throw new System.ComponentModel.Win32Exception(ret);
				}
				
				if (bRetVal)
				{
					var newId = new System.Security.Principal.WindowsIdentity(DuplicatedToken);
					string name = newId.Name;
					if (name != null) { }
					impersonatedUser = newId.Impersonate();
				}

				// Create process
				startInfo.cb = Marshal.SizeOf(startInfo);
				startInfo.lpDesktop = "winsta0\\default";

				bResult = Win32.CreateProcessAsUser(DuplicatedToken, null, strCommand, IntPtr.Zero, IntPtr.Zero,
													false, 0, IntPtr.Zero, null, ref startInfo, out processInfo);
				if (!bResult)
					throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());

				// Wait for process to end
				uiResultWait = WaitForSingleObject(processInfo.hProcess, INFINITE);
				if (uiResultWait == WAIT_FAILED)
					throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
			}
			finally
			{
				// Close all handles
				if (hToken != IntPtr.Zero)
					CloseHandle(hToken);
				if (DuplicatedToken != IntPtr.Zero)
					CloseHandle(DuplicatedToken);

				if (impersonatedUser != null)
				{
					impersonatedUser.Undo();
					impersonatedUser.Dispose();
				}

				CloseHandle(processInfo.hProcess);
				CloseHandle(processInfo.hThread);
			}
		}

		public static void LaunchCommand2OnDesktop(string deskName,string strCommand, string strDomain, string strName, string strPassword)
		{
			// Variables
			PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
			STARTUPINFO startInfo = new STARTUPINFO();
			Boolean bResult = false;
			IntPtr hToken = IntPtr.Zero;
			IntPtr DuplicatedToken = IntPtr.Zero;
			UInt32 uiResultWait = WAIT_FAILED;
			System.Security.Principal.WindowsImpersonationContext impersonatedUser = null;

			try
			{
				// Logon user
				bResult = Win32.LogonUser(strName, strDomain, strPassword,
											Win32.LogonType.LOGON32_LOGON_INTERACTIVE,
											Win32.LogonProvider.LOGON32_PROVIDER_DEFAULT,
											out hToken);
				if (!bResult)
					throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());

				SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
				sa.bInheritHandle = false;
				sa.nLength = Marshal.SizeOf(sa);
				sa.lpSecurityDescriptor = (IntPtr)0;
				uint GENERIC_ALL = 0x10000000;
				int TokenPrimary = 1;

				bool bRetVal = DuplicateTokenEx(hToken, GENERIC_ALL, ref sa, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TokenPrimary, out DuplicatedToken);
				if (bRetVal == false)
				{
					int ret = Marshal.GetLastWin32Error();
					CloseHandle(hToken);
					throw new System.ComponentModel.Win32Exception(ret);
				}

				if (bRetVal)
				{
					var newId = new System.Security.Principal.WindowsIdentity(DuplicatedToken);
					string name = newId.Name;
					if (name != null) { }
					impersonatedUser = newId.Impersonate();
				}

				// Create process
				startInfo.cb = Marshal.SizeOf(startInfo);
				//startInfo.lpDesktop = "winsta0\\default";
				startInfo.lpDesktop = deskName;

				bResult = Win32.CreateProcessAsUser(DuplicatedToken, null, strCommand, IntPtr.Zero, IntPtr.Zero,
													false, 0, IntPtr.Zero, null, ref startInfo, out processInfo);
				if (!bResult)
					throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());

				// Wait for process to end
				uiResultWait = WaitForSingleObject(processInfo.hProcess, INFINITE);
				if (uiResultWait == WAIT_FAILED)
					throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
			}
			finally
			{
				// Close all handles
				if (hToken != IntPtr.Zero)
					CloseHandle(hToken);
				if (DuplicatedToken != IntPtr.Zero)
					CloseHandle(DuplicatedToken);

				if (impersonatedUser != null)
				{
					impersonatedUser.Undo();
					impersonatedUser.Dispose();
				}

				CloseHandle(processInfo.hProcess);
				CloseHandle(processInfo.hThread);
			}
		}

		#endregion
	}
}